package it.tukano.collections;

import it.tukano.fun.Function0;
import it.tukano.fun.Function1;
import it.tukano.fun.Function2;
import it.tukano.fun.Tuple2;
import it.tukano.odt.OdtField;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * An extended list.
 * @author pgi
 * @param <T>
 */
public class ListExt<T> implements List<T>, Serializable {

    /* Serial version of this class */
    private static final long serialVersionUID = 0;

    /**
     * Returns a new listext with the elements of the given enumeration
     * @param <A> the type of the list ext
     * @param e the enumeration
     * @return a new list ext with the same elements as the enumeration
     */
    public static <A> ListExt<A> newListExt(Enumeration<A> e) {
        return ListExt.<A>newListExt(Collections.<A>list(e));
    }

    /**
     * Returns a new list ext backed by the given list
     * @param <A> the type of the elements of the list
     * @param list the list back end
     * @return a new list ext
     */
    public static <A> ListExt<A> newListExt(List<A> list) {
        return new ListExt<A>(list);
    }

    /**
     * Returns a new list ext with the contents of the given array
     * @param <A> the type of the list
     * @param values the values to add. Can be null or zero length
     * @return a new list ext with the values of the given array (or empty if
     * the array is null)
     */
    public static <A> ListExt<A> newListExt(A[] values) {
        if(values == null || values.length == 0) return new ArrayListExt<A>(0);
        return new ListExt<A>(Arrays.asList(values));
    }

    /**
     * Returns a new list ext with the values of the given collection of elements
     * @param <A> the type of the list ext
     * @param values the values to add
     * @return a new list ext (array or linked based depending on the type of values)
     */
    public static <A> ListExt<A> newListExt(Collection<? extends A> values) {
        return 
                values instanceof ArrayList ?
                    new ListExt<A>(new ArrayList<A>(values)) :
                    new ListExt<A>(new LinkedList<A>(values));
    }
    
    /**
     * Creates a new ListExt with the elements of an iterable instance
     * @param <A> the type of the list ext
     * @param values the values to add
     * @return a new list ext
     */
    public static <A> ListExt<A> newListExt(Iterable<? extends A> values) {
        ListExt<A> list = new LinkedListExt<A>();
        for (A a : values) {
            list.add(a);
        }
        return list;
    }
    
    /**
     * Returns a new ListExt backed by a LinkedList
     * @param <A> the type of the elements of the list
     * @return a new list ext
     */
    public static <A> ListExt<A> newLinkedListExt() {
        return new ListExt<A>(new LinkedList<A>());
    }

    /**
     * Returns a new ListExt backed by an ArrayList
     * @param <A> the type of the elements of the list
     * @return a new list ext
     */
    public static <A> ListExt<A> newArrayListExt() {
        return new ListExt<A>(new ArrayList<A>());
    }

    /** The backend of this list */
    private transient final List<T> source;
    
    /** The detached backend of this list */
    private transient List<T> detached;

    /**
     * Instance initializer
     */
    protected ListExt(List<T> source) {
        this.source = source;
    }
    
    public ListExt addAllNonNull(T... elements) {
        if(elements != null) for(int i = 0; i < elements.length; i++) {
            T e = elements[i];
            if(e != null) add(e);
        }
        return this;
    }

    /**
     * Removes the last element of the list
     * @return the removed element
     */
    public T removeLast() {
        T value = remove(size() - 1);
        return value;
    }
    
    /**
     * Sorts the elements of this list according to a sort function
     * @param sortFunction the sort function (like a comparator)
     * @return this list, sorted
     */
    public ListExt<T> sort(final Function2<T, T, Integer> sortFunction) {
        Collections.sort(source, new Comparator<T>() {

            public int compare(T o1, T o2) {
                return sortFunction.apply(o1, o2);
            }
        });
        return this;
    }

    /**
     * Add all the elements built with the given generator function. When the
     * generator returns null the process ends
     * @param generator the generator function
     * @return this list
     */
    public ListExt<T> addAll(Function0<T> generator) {
        for(T value = generator.apply(); value != null; value = generator.apply()) {
            add(value);
        }
        return this;
    }

    /**
     * Clear + addAll
     * @param items the items to insert in the cleared list
     */
    public void setAll(T[] items) {
        clear();
        addAll(Arrays.asList(items));
    }

    /**
     * Returns a read only view of this list
     * @return a read only view of this list
     */
    public ListExt<T> readOnly() {
        return new ListExt<T>(Collections.<T>unmodifiableList(this));
    }
    
    /**
     * Returns a map generated applying a function to the elements of this list.
     * Skips the null tuples
     * @param <A> the type of the key of the map
     * @param <B> the type of the value of the map
     * @param fun the generator function
     * @return the map
     */
    public <A, B> MapExt<A, B> toMap(final Function1<T, Tuple2<A, B>> fun) {
        MapExt<A, B> map = MapExt.newHashMap();
        for (T value : this) {
            Tuple2<A, B> tuple = fun.apply(value);
            if(tuple != null) {
                map.put(tuple.getA(), tuple.getB());
            }
        }
        return map;
    }

    /**
     * Converts the elements of this list into another list of elements
     * @param <K> the type of the converted list
     * @param conversion the conversion function
     * @return a new converted list
     */
    public <K> ListExt<K> convert(Function1<T, K> conversion) {
        ListExt<K> list = new LinkedListExt<K>();
        for (T value : this) {
            K convertedValue = conversion.apply(value);
            if(convertedValue != null) list.add(convertedValue);
        }
        return list;
    }

    /**
     * Returns an immutable view of this list
     * @return an immutable view of this list
     */
    public List<T> getImmutableView() {
        return Collections.unmodifiableList(this);
    }

    /**
     * Creates an internal copy of the contents of this list and clears this list
     */
    public void cacheAndClear() {
        if(detached == null) detached = new ArrayList<T>(this.size());
        detached.addAll(this);
        clear();
    }

    /**
     * Restores the internal copy of the contents of the list.
     */
    public void restoreCache() {
        if(detached == null) throw new IllegalStateException("Missing detach call");
        addAll(detached);
        detached.clear();
    }

    /**
     * Returns a copy of this list
     * @return a copy of this list
     */
    public ListExt<T> copy() {
        ListExt<T> list = new ListExt<T>(new ArrayList<T>(this));
        return list;
    }

    /**
     * Add all the elements of an iterator
     * @param i the iterator
     * @return this
     */
    public ListExt<T> add(Iterable<? extends T> i) {
        for (T object : i) {
            add(object);
        }
        return this;
    }

    /**
     * Append an element and return this
     * @param item the item to append
     * @return this
     */
    public ListExt<T> append(T item) {
        add(item);
        return this;
    }

    public int size() {
        return source.size();
    }

    public boolean isEmpty() {
        return source.isEmpty();
    }

    public boolean contains(Object o) {
        return source.contains(o);
    }

    public Iterator<T> iterator() {
        return source.iterator();
    }

    public Object[] toArray() {
        return source.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return source.toArray(a);
    }
    
    public ListExt<T> addAll(T[] items) {
        if(items != null) for(int i = 0; i < items.length; i++) {
            add(items[i]);
        }
        return this;
    }

    public boolean add(T e) {
        return source.add(e);
    }

    public boolean remove(Object o) {
        return source.remove(o);
    }

    public boolean containsAll(Collection<?> c) {
        return source.containsAll(c);
    }

    public boolean addAll(Collection<? extends T> c) {
        return source.addAll(c);
    }

    public boolean addAll(int index, Collection<? extends T> c) {
        return source.addAll(index, c);
    }

    public boolean removeAll(Collection<?> c) {
        return source.removeAll(c);
    }

    public boolean retainAll(Collection<?> c) {
        return source.retainAll(c);
    }

    public void clear() {
        source.clear();
    }

    public T get(int index) {
        return source.get(index);
    }

    public T set(int index, T element) {
        return source.set(index, element);
    }

    public void add(int index, T element) {
        source.add(index, element);
    }

    public T remove(int index) {
        return source.remove(index);
    }

    public int indexOf(Object o) {
        return source.indexOf(o);
    }

    public int lastIndexOf(Object o) {
        return source.lastIndexOf(o);
    }

    public ListIterator<T> listIterator() {
        return source.listIterator();
    }

    public ListIterator<T> listIterator(int index) {
        return source.listIterator(index);
    }

    public List<T> subList(int fromIndex, int toIndex) {
        return source.subList(fromIndex, toIndex);
    }

    public ListExt<T> find(Function1<T, Boolean> filter) {
        final ListExt<T> list = ListExt.<T>newLinkedListExt();
        for (T value : this) {
            if(filter.apply(value)) list.add(value);
        }
        return list;
    }

    public <K> ListExt<K> filter(Function1<T, K> fun) {
        final ListExt<K> list = ListExt.<K>newLinkedListExt();
        for(T value : this) {
            K res = fun.apply(value);
            if(res != null) {
                list.add(res);
            }
        }
        return list;
    }

    public boolean hasRemaining() {
        return size() > 0;
    }

    public T pop() {
        return remove(0);
    }

    public T getOrElse(int index, T defaultValue) {
        if(index < size()) {
            return get(index);
        } else {
            return defaultValue;
        }
    }

    public T getLast() {
        return get(size() - 1);
    }

    public String toString(String separator) {
        StringBuilder buffer = new StringBuilder();
        for (T t : this) {
            buffer.append(t).append(separator);
        }
        return buffer.toString();
    }
}
